// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <zircon/syscalls/object.h>
#include <zircon/time.h>
#include <zircon/types.h>

#include <utility>

#include <fbl/algorithm.h>
#include <intel-hda/utils/utils.h>

namespace audio {
namespace intel_hda {

zx_status_t WaitCondition(zx_duration_t timeout, zx_duration_t poll_interval,
                          WaitConditionFn cond) {
  ZX_DEBUG_ASSERT(poll_interval != ZX_TIME_INFINITE);
  ZX_DEBUG_ASSERT(cond);

  zx_time_t now = zx_clock_get_monotonic();
  zx_time_t deadline = zx_time_add_duration(now, timeout);

  while (!cond()) {
    now = zx_clock_get_monotonic();
    if (now >= deadline)
      return ZX_ERR_TIMED_OUT;

    zx_duration_t sleep_time = zx_time_sub_time(deadline, now);
    if (poll_interval < sleep_time)
      sleep_time = poll_interval;

    zx_nanosleep(zx_deadline_after(sleep_time));
  }

  return ZX_OK;
}

fbl::RefPtr<RefCountedBti> RefCountedBti::Create(zx::bti initiator) {
  fbl::AllocChecker ac;

  auto ret = fbl::AdoptRef(new (&ac) RefCountedBti(std::move(initiator)));
  if (!ac.check()) {
    return nullptr;
  }

  return ret;
}

static constexpr audio_sample_format_t AUDIO_SAMPLE_FORMAT_UNSIGNED_8BIT =
    static_cast<audio_sample_format_t>(AUDIO_SAMPLE_FORMAT_8BIT |
                                       AUDIO_SAMPLE_FORMAT_FLAG_UNSIGNED);

static constexpr audio_sample_format_t AUDIO_SAMPLE_FORMAT_NONE =
    static_cast<audio_sample_format_t>(0u);

struct FrameRateLut {
  uint32_t flag;
  uint32_t rate;
};

// Note: these LUTs must be kept in monotonically ascending order.
static const FrameRateLut FRAME_RATE_LUT_48K[] = {
    {IHDA_PCM_RATE_8000, 8000},     {IHDA_PCM_RATE_16000, 16000}, {IHDA_PCM_RATE_32000, 32000},
    {IHDA_PCM_RATE_48000, 48000},   {IHDA_PCM_RATE_96000, 96000}, {IHDA_PCM_RATE_192000, 192000},
    {IHDA_PCM_RATE_384000, 384000},
};

static const FrameRateLut FRAME_RATE_LUT_44_1K[] = {
    {IHDA_PCM_RATE_11025, 11025}, {IHDA_PCM_RATE_22050, 22050},   {IHDA_PCM_RATE_44100, 44100},
    {IHDA_PCM_RATE_88200, 88200}, {IHDA_PCM_RATE_176400, 176400},
};

static const struct {
  const FrameRateLut* lut;
  size_t lut_size;
  uint16_t family_flag;
} FRAME_RATE_LUTS[] = {
    {FRAME_RATE_LUT_48K, fbl::count_of(FRAME_RATE_LUT_48K), ASF_RANGE_FLAG_FPS_48000_FAMILY},
    {FRAME_RATE_LUT_44_1K, fbl::count_of(FRAME_RATE_LUT_44_1K), ASF_RANGE_FLAG_FPS_44100_FAMILY},
};

zx_obj_type_t GetHandleType(const zx::handle& handle) {
  zx_info_handle_basic_t basic_info;

  if (!handle.is_valid())
    return ZX_OBJ_TYPE_NONE;

  zx_status_t res =
      handle.get_info(ZX_INFO_HANDLE_BASIC, &basic_info, sizeof(basic_info), nullptr, nullptr);

  return (res == ZX_OK) ? static_cast<zx_obj_type_t>(basic_info.type) : ZX_OBJ_TYPE_NONE;
}

zx_status_t MakeFormatRangeList(const SampleCaps& sample_caps, uint32_t max_channels,
                                fbl::Vector<audio_stream_format_range_t>* ranges) {
  if (ranges == nullptr || ranges->size())
    return ZX_ERR_INVALID_ARGS;
  if (!max_channels)
    return ZX_ERR_INVALID_ARGS;

  // Signed and unsigned formats require separate audio_sample_format_t
  // encodings.  8-bit is the only unsigned format supported by IHDA, however.
  // Compute the set signed formats that this stream supports, check for
  // unsigned 8 bit support in the process.
  auto signed_formats = AUDIO_SAMPLE_FORMAT_NONE;
  bool unsigned_8bit_supported = false;

  if (sample_caps.pcm_formats_ & IHDA_PCM_FORMAT_PCM) {
    static const struct {
      uint32_t ihda_flag;
      audio_sample_format_t audio_flag;
    } FORMAT_LUT[] = {
        {IHDA_PCM_SIZE_32BITS, AUDIO_SAMPLE_FORMAT_32BIT},
        {IHDA_PCM_SIZE_24BITS, AUDIO_SAMPLE_FORMAT_24BIT_IN32},
        {IHDA_PCM_SIZE_20BITS, AUDIO_SAMPLE_FORMAT_20BIT_IN32},
        {IHDA_PCM_SIZE_16BITS, AUDIO_SAMPLE_FORMAT_16BIT},
    };

    for (const auto& f : FORMAT_LUT) {
      if (sample_caps.pcm_size_rate_ & f.ihda_flag) {
        signed_formats = static_cast<audio_sample_format_t>(signed_formats | f.audio_flag);
      }
    }

    if (sample_caps.pcm_size_rate_ & IHDA_PCM_SIZE_8BITS) {
      unsigned_8bit_supported = true;
    }
  }

  // If float is supported, add that into the set of signed formats that we
  // support.
  if (sample_caps.pcm_formats_ & IHDA_PCM_FORMAT_FLOAT32) {
    signed_formats =
        static_cast<audio_sample_format_t>(signed_formats | AUDIO_SAMPLE_FORMAT_32BIT_FLOAT);
  }

  // If we do not support any sample formats, simply get out early.  There is
  // no point in trying to compute the frame rate ranges.
  if (!signed_formats && !unsigned_8bit_supported)
    return ZX_OK;

  // Next, produce the sets of frame rates in the 48 and 44.1KHz frame rate
  // families which can be expressed using the [min, max] notation.  In
  // theory, it might be possible to combine each of these into a single
  // audio_stream_format_range_t entry, but to keep things simple, we don't
  // try to do so.
  for (const auto& family : FRAME_RATE_LUTS) {
    bool active_range = false;
    uint32_t min_rate = 0, max_rate = 0;

    for (size_t i = 0; i < family.lut_size; ++i) {
      const auto& entry = family.lut[i];
      bool supported_rate = (sample_caps.pcm_size_rate_ & entry.flag) != 0;

      // If this rate is supported, then either start a new range of
      // contiguous rates (if this is the first in this range) or bump up
      // the max rate if we are already building a range.
      if (supported_rate) {
        if (!active_range) {
          min_rate = entry.rate;
          max_rate = entry.rate;
          active_range = true;
        } else {
          max_rate = entry.rate;
        }
      }

      // If we have an active range of contiguous rates, and we have
      // either encountered a gap in the supported rates or this is the
      // last rate in the family, then produce the format ranges needed
      // for this range of rates.
      if (active_range && (!supported_rate || (i == family.lut_size))) {
        audio_stream_format_range_t range;

        range.min_frames_per_second = min_rate;
        range.max_frames_per_second = max_rate;
        range.min_channels = 1;
        range.max_channels = static_cast<uint8_t>(max_channels);
        range.flags = family.family_flag;

        if (signed_formats) {
          range.sample_formats = signed_formats;

          fbl::AllocChecker ac;
          ranges->push_back(range, &ac);
          if (!ac.check())
            return ZX_ERR_NO_MEMORY;
        }

        if (unsigned_8bit_supported) {
          range.sample_formats = AUDIO_SAMPLE_FORMAT_UNSIGNED_8BIT;

          fbl::AllocChecker ac;
          ranges->push_back(range, &ac);
          if (!ac.check())
            return ZX_ERR_NO_MEMORY;
        }

        active_range = false;
      }
    }
  }

  return ZX_OK;
}

}  // namespace intel_hda
}  // namespace audio
